package durst.action.account;

import java.io.StringReader;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.StreamingResolution;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;

import com.sdicons.json.mapper.JSONMapper;
import com.sdicons.json.mapper.MapperException;
import com.sdicons.json.model.JSONArray;
import com.sdicons.json.model.JSONBoolean;
import com.sdicons.json.model.JSONObject;
import com.sdicons.json.model.JSONValue;

import durst.action.CalendarBasicAction;
import durst.data.Event;
import durst.data.Event.Range;
import durst.data.Friend;
import durst.data.Invite;
import durst.data.UserCalendar;
import durst.data.UserCalendar.SharedStatus;
import durst.data.dao.DaoFactory;
import durst.data.dao.impl.FriendDao;
import durst.data.dao.impl.InviteDao;
import durst.data.dao.impl.UserCalendarDao;
import durst.exception.AccessDeniedException;

public class CalendarActionBean extends CalendarBasicAction {
	public static final int NO_CAL = -1;
	private static final int DAYS_IN_WEEK = 7;
	private static final int WEEKS_VIEW = 6;

	@ValidateNestedProperties({ @Validate(field = "id", minvalue = 1) })
	private UserCalendar cal;

	@Validate(required = true, on = "!view", minvalue = Calendar.JANUARY, maxvalue = Calendar.DECEMBER)
	private int month;

	@Validate(required = true, on = "!view")
	private int year;

	@DefaultHandler
	public Resolution view() throws SQLException, MapperException {
		month = Calendar.getInstance().get(Calendar.MONTH);
		year = Calendar.getInstance().get(Calendar.YEAR);

		context.getRequest().setAttribute("jsonObj",
				buildMonth(Calendar.getInstance(), false).render(false));
		context.getRequest().setAttribute("cal", cal);
		return new ForwardResolution("/account/calendar.jsp");
	}

	public Resolution today() throws SQLException, MapperException {
		return new StreamingResolution("application/json", new StringReader(buildMonth(
				Calendar.getInstance(), true).render(false)));
	}

	public Resolution same() throws SQLException, MapperException {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);

		return new StreamingResolution("application/json", new StringReader(buildMonth(cal, true)
				.render(false)));
	}

	public Resolution prev() throws SQLException, MapperException {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, year);

		cal.set(Calendar.MONTH, month);
		cal.add(Calendar.MONTH, -1);

		while (cal.get(Calendar.MONTH) == month)
			cal.add(Calendar.DATE, -1); // fixes bug for when current month has
										// more days then prev month

		return new StreamingResolution("application/json", new StringReader(buildMonth(cal, true)
				.render(false)));
	}

	public Resolution next() throws SQLException, MapperException {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, year);

		cal.set(Calendar.MONTH, month);
		cal.add(Calendar.MONTH, 1);

		while (cal.get(Calendar.MONTH) > month + 1)
			cal.add(Calendar.DATE, -1);

		return new StreamingResolution("application/json", new StringReader(buildMonth(cal, true)
				.render(false)));
	}

	@SuppressWarnings("unchecked")
	private JSONValue buildMonth(Calendar target, boolean json) throws SQLException,
			MapperException {
		UserCalendarDao<UserCalendar, Integer> calDao = (UserCalendarDao<UserCalendar, Integer>) DaoFactory
				.getInstance().createDao(UserCalendar.class);
		boolean isFriendOf = false;

		if (cal == null || cal.getId() == 0) {
			cal = calDao.getDefaultCalendarForAccount(context.getAccount());
			isFriendOf = true;
		}
		else {
			calDao.refresh(cal);

			if (cal.getShared() != SharedStatus.PUBLIC
					&& !cal.getOwner().equals(context.getAccount())) {
				if (cal.getShared() == SharedStatus.FRIENDS) {
					FriendDao<Friend, Integer> friendDao = (FriendDao<Friend, Integer>) DaoFactory
							.getInstance().createDao(Friend.class);

					isFriendOf = friendDao.isFriendOf(cal.getOwner(), context.getAccount());
				}

				Calendar forever = Calendar.getInstance();
				forever.setTimeInMillis(0L);

				if (!isFriendOf
						&& !((InviteDao<Invite, Integer>) DaoFactory.getInstance().createDao(
								Invite.class)).getInvitedCalendars(context.getAccount(),
								forever.getTime(), false).contains(cal))
					throw new AccessDeniedException(json, context);
			}
			else
				isFriendOf = true;
		}

		Calendar start = (Calendar) target.clone();
		start.add(
				Calendar.DATE,
				-1
						* ((target.get(Calendar.WEEK_OF_MONTH) - 1) * DAYS_IN_WEEK
								+ target.get(Calendar.DAY_OF_WEEK) - 1));
		start.set(Calendar.HOUR_OF_DAY, 0);
		start.set(Calendar.MINUTE, 0);
		start.set(Calendar.SECOND, 0);
		start.set(Calendar.MILLISECOND, 0);

		Calendar end = (Calendar) start.clone();
		end.add(Calendar.DATE, DAYS_IN_WEEK * WEEKS_VIEW);
		end.add(Calendar.MILLISECOND, -1);

		List<Event> events = new LinkedList<Event>();
		for (Event e : isFriendOf ? cal.getEvents() : ((InviteDao<Invite, Integer>) DaoFactory
				.getInstance().createDao(Invite.class)).getInvites(context.getAccount(), cal)) {
			if (e.eventInRange(start, end) == Range.INSIDE)
				events.add(e);
			if (e.isRepeated())
				events.addAll(e.getFrequency().getRepeats(e, start, end));
		}
		Collections.sort(events);

		JSONArray days = new JSONArray();
		Calendar tomorrow = (Calendar) start.clone();
		tomorrow.add(Calendar.MILLISECOND, -1);

		Calendar now = Calendar.getInstance();

		Map<String, Object> ret = new HashMap<String, Object>();
		ret.put("startTime", new Integer[] { start.get(Calendar.YEAR), start.get(Calendar.MONTH),
				start.get(Calendar.DATE) });
		for (int d = 0; !start.after(end); d++) {
			tomorrow.add(Calendar.DATE, 1);
			boolean today = now.after(start) && now.before(tomorrow);
			boolean otherMonth = start.get(Calendar.MONTH) != target.get(Calendar.MONTH);

			JSONObject day = new JSONObject();

			if (!otherMonth && ret.get("activeDay") == null) {
				ret.put("activeDay", d);
				ret.put("otherdays", d);
			}
			else if (today) {
				ret.put("activeDay", d);
				if (ret.get("otherdays") == null)
					ret.put("otherdays", 0);
			}

			day.getValue().put("date", JSONMapper.toJSON(start.get(Calendar.DATE)));
			day.getValue().put("events",
					JSONMapper.toJSON(Event.eventsInRange(events, start, tomorrow)));
			day.getValue().put("today", new JSONBoolean(today));
			day.getValue().put("otherMonth", new JSONBoolean(otherMonth));
			days.getValue().add(day);
			start.add(Calendar.DATE, 1);
		}

		assert (days.size() <= DAYS_IN_WEEK * WEEKS_VIEW);

		StringBuilder headerBuilder = new StringBuilder(cal.getName());
		headerBuilder.append(" - ");
		headerBuilder.append(target.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH));
		headerBuilder.append(" ");
		headerBuilder.append(target.get(Calendar.YEAR));

		ret.put("header", headerBuilder.toString());
		ret.put("month", target.get(Calendar.MONTH));
		ret.put("year", target.get(Calendar.YEAR));
		ret.put("isNow",
				target.get(Calendar.MONTH) == now.get(Calendar.MONTH)
						&& target.get(Calendar.YEAR) == now.get(Calendar.YEAR));
		ret.put("account", cal.getOwner().getId());
		ret.put("editable", cal.getOwner().equals(context.getAccount()));
		
		JSONObject obj = (JSONObject) JSONMapper.toJSON(ret);
		obj.getValue().put("days", days);
		return obj;
	}

	public CalendarActionBean() {
	}

	public void setMonth(int month) {
		this.month = month;
	}

	public int getMonth() {
		return month;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public int getYear() {
		return year;
	}

	public void setCal(UserCalendar cal) {
		this.cal = cal;
	}

	public UserCalendar getCal() {
		return cal;
	}
}
